package util

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	mathrand "math/rand"
	"mime"
	"net"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

func EncodeJson(data interface{}) ([]byte, error) {
	body, err := json.Marshal(data)

	if err != nil {
		return nil, err
	}
	return body, nil
}

func DecodeReader(r io.Reader) (map[string]interface{}, error) {
	var m map[string]interface{}
	dec := json.NewDecoder(r)
	if err := dec.Decode(&m); err != nil {
		return nil, err
	}
	return m, nil
}

func DecodeJson(data []byte) (map[string]interface{}, error) {
	var m map[string]interface{}

	err := json.Unmarshal(data, &m)

	if err != nil {
		return nil, err
	}
	return m, nil
}

func Call(method, baseUrl, path string, body io.Reader, headers map[string][]string) ([]byte, int, error) {
	client := &http.Client{}
	fmt.Println(baseUrl + path)
	req, err := http.NewRequest(method, baseUrl+path, body)
	if err != nil {
		return nil, 408, err
	}

	req.Header.Set("User-Agent", "XENIUMD-AGENT")
	if method == "POST" {
		req.Header.Set("Content-Type", "plain/text")
	}

	if headers != nil {
		for k, v := range headers {
			req.Header[k] = v
		}
	}
	resp, err := client.Do(req)
	if err != nil {
		return nil, 0, err
	}

	dataBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, 0, err
	}
	defer resp.Body.Close()

	return dataBody, resp.StatusCode, nil
}

func Response(data []byte, statusCode int, resp http.ResponseWriter) {
	resp.WriteHeader(statusCode)
	fmt.Fprintf(resp, string(data))
}

func NotFoundHandle(resp http.ResponseWriter, req *http.Request) {
	fmt.Fprintf(resp, "Not Found this page.")
}

/*
func HandleError(resp http.ResponseWriter, respData string, err error, respCode int) {
	if err != nil {
		log.Errorf("resp err. %v", err)
	}

	log.Errorf("respCode %d.", respCode)
	resp.WriteHeader(respCode)

	log.Errorf("respData %s.", respData)
	fmt.Fprintf(resp, respData)
}*/

func HandleErrs(resp http.ResponseWriter, respCode int, err error, state int) {
	resp.WriteHeader(respCode)
	data := fmt.Sprintf("{\"state\": %d, \"error\":\"%s\"}", state, err.Error())
	fmt.Fprintf(resp, data)
}

func HandleErrors(resp http.ResponseWriter, err error, respCode int) {
	resp.WriteHeader(respCode)
	fmt.Fprintf(resp, err.Error())
}

func ParseRepositoryTag(repos string) (string, string) {
	n := strings.LastIndex(repos, ":")
	if n < 0 {
		return repos, ""
	}
	if tag := repos[n+1:]; !strings.Contains(tag, "/") {
		return repos[:n], tag
	}
	return repos, ""
}

func MatchsContentType(contentType, expectedType string) bool {
	mimetype, _, err := mime.ParseMediaType(contentType)

	if err != nil {
		fmt.Errorf("Error parsing media type: %s error: %s", contentType, err.Error())
	}

	return err == nil && mimetype == expectedType
}

func CheckForJson(r *http.Request) error {
	ct := r.Header.Get("Content-Type")

	// No Content-Type header is ok as long as there's no Body
	if ct == "" {
		if r.Body == nil || r.ContentLength == 0 {
			return nil
		}
	}

	// Otherwise it better be json
	if MatchsContentType(ct, "application/json") {
		return nil
	}

	return fmt.Errorf("Content-Type spcified (%s) must be 'application/json'", ct)
}

// TruncateID returns a shorthand version of a string identifier for convenience.
// A collision with other shorthands is very unlikely, but possible.
// In case of a collision a lookup with TruncIndex.Get() will fail, and the caller
// will need to use a langer prefix, or the full-length Id.
func truncateID(id string) string {
	shortLen := 12
	if len(id) < shortLen {
		shortLen = len(id)
	}
	return id[:shortLen]
}

// GenerateRandomID returns an unique id
func GenerateRandomID() string {
	for {
		id := make([]byte, 16)
		if _, err := io.ReadFull(rand.Reader, id); err != nil {
			panic(err) // This shouldn't happen
		}
		value := hex.EncodeToString(id)
		// if we try to parse the truncated for as an int and we don't have
		// an error then the value is all numberic and causes issues when
		// used as a hostname. ref #3869
		if _, err := strconv.ParseInt(truncateID(value), 10, 32); err == nil {
			continue
		}
		return value
	}
}

func RunCommand(name string, args ...string) (string, error) {
	cmd := exec.Command(name, args...)
	buf, _ := cmd.Output()
	output := string(buf)
	output = strings.TrimSuffix(output, "\n")
	return output, nil
}

func RunShellCommand(shell string) (string, error) {
	cmd := exec.Command("/bin/sh", "-c", shell)
	buf, err := cmd.CombinedOutput()
	output := string(buf)
	output = strings.TrimSuffix(output, "\n")
	return output, err
}

func RandMacAddr() string {
	hw := make(net.HardwareAddr, 6)
	for i := 0; i < 6; i++ {
		hw[i] = byte(mathrand.Intn(255))
	}
	hw[0] &^= 0x1 // clear multicast bit
	hw[0] |= 0x2  // set local assignment bit (IEEE802)
	return hw.String()
}

func CalcCidr(ipMask string) string {
	ip := strings.Split(ipMask, "/")[0]
	mask := strings.Split(ipMask, "/")[1]
	bits := strings.Split(ip, ".")
	b0, _ := strconv.Atoi(bits[0])
	b1, _ := strconv.Atoi(bits[1])
	b2, _ := strconv.Atoi(bits[2])
	b3, _ := strconv.Atoi(bits[3])
	c0 := int64(b0)
	c1 := int64(b1)
	c2 := int64(b2)
	c3 := int64(b3)
	var cidr string
	var (
		m0 string
		m1 string
		m2 string
		m3 string
	)
	if strings.Index(mask, ".") == -1 {
		maskInt, _ := strconv.Atoi(mask)
		var maskStr string = ""
		for i := 0; i < 32; i++ {
			if i < maskInt {
				maskStr += "1"
			} else {
				maskStr += "0"
			}
		}
		m0 = maskStr[:8]
		m1 = maskStr[8:16]
		m2 = maskStr[16:24]
		m3 = maskStr[24:32]
	} else {
		maskStrList := strings.Split(mask, ".")
		maskInt0, _ := strconv.ParseInt(maskStrList[0], 10, 0)
		maskInt1, _ := strconv.ParseInt(maskStrList[1], 10, 0)
		maskInt2, _ := strconv.ParseInt(maskStrList[2], 10, 0)
		maskInt3, _ := strconv.ParseInt(maskStrList[3], 10, 0)
		m0 = strconv.FormatInt(maskInt0, 2)
		m1 = strconv.FormatInt(maskInt1, 2)
		m2 = strconv.FormatInt(maskInt2, 2)
		m3 = strconv.FormatInt(maskInt3, 2)
	}
	k0, _ := strconv.ParseInt(m0, 2, 0)
	k1, _ := strconv.ParseInt(m1, 2, 0)
	k2, _ := strconv.ParseInt(m2, 2, 0)
	k3, _ := strconv.ParseInt(m3, 2, 0)
	cidr = strconv.Itoa(int(c0&k0)) + "." + strconv.Itoa(int(c1&k1)) + "." + strconv.Itoa(int(c2&k2)) + "." + strconv.Itoa(int(c3&k3)) + "/" + mask
	return cidr
}

func AssertArrayInterface(params interface{}) []map[string]interface{} {
	var resp []map[string]interface{}
	var assertBody interface{} = params
	if result, ok := assertBody.([]map[string]interface{}); ok {
		resp = result
	}
	if result, ok := assertBody.([]interface{}); ok {
		var resMapList []map[string]interface{} = make([]map[string]interface{}, 0, len(result))
		for i := 0; i < len(result); i++ {
			resMapList = append(resMapList, result[i].(map[string]interface{}))
		}
		resp = resMapList
	}
	return resp
}

func WriteFile(filePath, content string) (err error) {
	_, err = os.Stat(filePath)
	if err == nil {
		return nil
	}
	f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		panic(err)
	}
	defer f.Close()
	f.WriteString(content)
	return nil
}

func ReadFile(filePath string) (string, error) {
	cnt, err := ioutil.ReadFile(filePath)
	if err != nil {
		return "", fmt.Errorf("read file error, filePath:%s err:%v", filePath, err)
	}

	return string(cnt), nil
}

func CheckValue(v interface{}, vType string) error {
	if v == nil {
		return fmt.Errorf("value is nil")
	}

	switch v.(type) {
	case int:
		if vType != "int" {
			return fmt.Errorf("%v is not %s type", v, vType)
		}
	case float64:
		if vType != "float64" {
			return fmt.Errorf("%v is not %s type", v, vType)
		}
	case string:
		if vType != "string" {
			return fmt.Errorf("%v is not %s type", v, vType)
		}
	case bool:
		if vType != "bool" {
			return fmt.Errorf("%v is not %s type", v, vType)
		}
	case []interface{}:
		if vType != "[]interface {}" {
			return fmt.Errorf("%v is not %s type", v, vType)
		}
	case map[string]interface{}:
		if vType != "map[string]interface {}" {
			return fmt.Errorf("%v is not %s type", v, vType)
		}
	default:
		return fmt.Errorf("<unknow data type>")
	}

	return nil
}

func CheckMapString(m map[string]interface{}, key string) (string, error) {
	if _, ok := m[key]; !ok {
		return "", fmt.Errorf(key + " is nil")
	}

	if v, ok := m[key].(string); ok {
		return v, nil
	}

	return "", fmt.Errorf(key + " is not string type")
}

func CheckMapBool(m map[string]interface{}, key string) (bool, error) {
	if _, ok := m[key]; !ok {
		return false, fmt.Errorf(key + " is nil")
	}

	if v, ok := m[key].(bool); ok {
		return v, nil
	}

	return false, fmt.Errorf(key + " is not bool type")
}

func CheckMapInt64(m map[string]interface{}, key string) (int64, error) {
	if _, ok := m[key]; !ok {
		return 0, fmt.Errorf(key + " is nil")
	}

	if v, ok := m[key].(float64); ok {
		return int64(v), nil
	}

	return 0, fmt.Errorf(key + " is not int64 type")
}

func CheckMapInt32(m map[string]interface{}, key string) (int32, error) {
	if _, ok := m[key]; !ok {
		return 0, fmt.Errorf(key + " is nil")
	}

	if v, ok := m[key].(float64); ok {
		return int32(v), nil
	}

	return 0, fmt.Errorf(key + " is not int32 type")
}

func CheckMapInt(m map[string]interface{}, key string) (int, error) {
	if _, ok := m[key]; !ok {
		return 0, fmt.Errorf(key + " is nil")
	}

	if v, ok := m[key].(float64); ok {
		return int(v), nil
	}

	return 0, fmt.Errorf(key + " is not int type")
}

func CheckMapUInt16(m map[string]interface{}, key string) (uint16, error) {
	if _, ok := m[key]; !ok {
		return 0, fmt.Errorf(key + " is nil")
	}

	if v, ok := m[key].(float64); ok {
		return uint16(v), nil
	}

	return 0, fmt.Errorf(key + " is not uint16 type")
}

func CheckMapUInt32(m map[string]interface{}, key string) (uint32, error) {
	if _, ok := m[key]; !ok {
		return 0, fmt.Errorf(key + " is nil")
	}

	if v, ok := m[key].(float64); ok {
		return uint32(v), nil
	}

	return 0, fmt.Errorf(key + " is not uint32 type")
}

func CheckMapInterface(m map[string]interface{}, key string) ([]interface{}, error) {
	if _, ok := m[key]; !ok {
		return nil, fmt.Errorf(key + " is nil")
	}

	if v, ok := m[key].([]interface{}); ok {
		return []interface{}(v), nil
	}

	return nil, fmt.Errorf(key + " is not []interface{} type")
}

func Base64Encode(src []byte) string {
	return base64.StdEncoding.EncodeToString(src)
}

func Base64Decode(src []byte) (string, error) {
	data, err := base64.StdEncoding.DecodeString(string(src))
	if err != nil {
		return string(data), err
	}
	return string(data), nil
}

func GetCurrentUnix() int64 {
	return time.Now().Unix()
}

func GetDiffTime(tm int64) int64 {
	return time.Now().Unix() - tm
}

func FormatDiffTime(tm int64) string {
	return fmt.Sprintf("time diff:%v", GetDiffTime(tm))
}

/**
 * IsDirExists - whether the path exist
 * @path: path name
 */
func IsDirExists(path string) bool {
	fi, err := os.Stat(path)
	if err != nil {
		return os.IsExist(err)
	} else {
		return fi.IsDir()
	}
	panic("not reached")
}

/**
 * IsFileExists - whether the file exist
 * @filename: file full path name
 */
func IsFileExists(filename string) bool {
	fi, err := os.Stat(filename)
	if err != nil {
		return os.IsExist(err)
	} else {
		return !fi.IsDir()
	}
	return false
}

/**
 * ParseInterface - check the m's type, if not map[string]interface{} then continue
 * @m: data
 */
func ParseInterface(m []interface{}) ([]map[string]interface{}, error) {
	mapslice := make([]map[string]interface{}, 0)

	for _, value := range m {
		switch value.(type) {
		case map[string]interface{}:
			mapslice = append(mapslice, value.(map[string]interface{}))
		default:
		}
	}
	return mapslice, nil
}

func Request(method, baseUrl, path string, body io.Reader) ([]byte, int, error) {
	client := &http.Client{}
	url := fmt.Sprintf("%s%s", baseUrl, path)
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return nil, 0, err
	}

	req.Header.Set("Content-Type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		return nil, 0, err
	}

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, 0, err
	}
	defer resp.Body.Close()
	return data, resp.StatusCode, nil
}

func GetRandUUID() string {
	f, _ := os.OpenFile("/dev/urandom", os.O_RDONLY, 0)
	b := make([]byte, 16)
	f.Read(b)
	f.Close()
	uuid := fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
	return uuid
}

func GetLocalIp() ([]string, error) {
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		err = fmt.Errorf("get InterfaceAddrs error, err:%v", err)
		return nil, err
	}

	iplist := make([]string, 0)

	for _, a := range addrs {
		if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				iplist = append(iplist, ipnet.IP.String())
			}
		}
	}

	count := len(iplist)
	if count == 0 {
		err = fmt.Errorf("get ip count failure, count:%v", count)
		return nil, err
	}
	
	return iplist, nil
}

func StringToUint64(arrays []string) ([]uint64, error) {
	rv := make([]uint64, len(arrays))
	for i, array := range arrays {
		v, err := strconv.Atoi(array)
		if err != nil {
			return nil, err
		}
		rv[i] = uint64(v)
	}
	
	return rv, nil
}